/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 huliqing, huliqing.cn@gmail.com
 *
 * This file is part of QFaces.
 * QFaces is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * QFaces is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with QFaces.  If not, see <http://www.gnu.org/licenses/>.
 *
 * 这个文件是QFaces的一部分。
 * 您可以单独使用或分发这个文件，但请不要移除这个头部声明信息.
 * QFaces是一个自由软件，您可以自由分发、修改其中的源代码或者重新发布它，
 * 新的任何修改后的重新发布版必须同样在遵守LGPL3或更后续的版本协议下发布.
 * 关于LGPL协议的细则请参考COPYING、COPYING.LESSER文件，
 * 您可以在QFaces的相关目录中获得LGPL协议的副本，
 * 如果没有找到，请连接到 http://www.gnu.org/licenses/ 查看并获取副本。
 *
 * - Author: Huliqing
 * - Contact: huliqing.cn@gmail.com
 * - License: GNU Lesser General Public License (LGPL)
 * - Blog and source code availability: http://www.huliqing.name/
 */

package name.huliqing.qfaces.component;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.MethodExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import name.huliqing.qfaces.QFaces;
import name.huliqing.qfaces.XmlOper;
import name.huliqing.qfaces.model.MiniPageModel;
import name.huliqing.qfaces.model.MiniPageParam;
  
public class MiniDataTable extends UIComponentBase implements Ajax{
	
	private Logger logger = java.util.logging.Logger.getLogger(MiniDataTable.class.getName());
	
	// ---- 组件参数
	
	private MethodExpression search = null;
	private String _ref;				// id1,id2,id3
	private String _for;				// id1,id2,id3
	private String _sortFields;			// field1,field2,field3
	private String _strHeaders;			// dataTable的header区域的字串，由","分隔
	private String _strOpenButtonTip;	// Open Button按钮的提示信息
	private Integer _pageSize;			// 每页显示数
	private Integer _displayPage;		// 最多显示多少页
	private Boolean _autoClose;			// 是否自动关闭inner区域(在用户选择数据之后)
	private Boolean _searchWhenOpen;	// 第一次打开时进行搜索

	private Boolean alwaysShow;			// 是否总是显示
	private Boolean showOpenButton;		// 是否显示开关
	private String border;				// table的border属性
	private String styleClass;			// table的css样式
	private String headerClass;			// table头的css属性
	private String rowClasses;			// table行的css属性
	private String columnClasses;		// table列的css属性，e.g.  columnClasses="col1,col2,..."
	private String rowOnClass;			// table行的css属性（当鼠标放上去时）
	private String ascSrc;				// 正排序箭头的图片的src
	private String descSrc;				// 正排序箭头的图片的src
	private Boolean displayJump;		// 是否显示跳转框
	private String strJump;				// 跳转框的label
	private Boolean displayInfo;		// 是否显示总记录数及总页数
	private String strRecordTotal;		// "总记录数"的文字描述
	private String strPageTotal;		// "总页数"的文字描述
	private String scrollerOn;			// top, bottom, both. scroller的渲染位置
	private Boolean alwaysShowScroller;	// true / false, default true.如果为false,那么当数据<=1页时将不显示scroller
	private String scrollerClass;			// scroller css class name
	private String scrollerInfoClass;		// scroller统计信息的css class
	private String scrollerPageClass;		// css
	private String scrollerPageCurrentClass;// css
	private String scrollerPageOnClass;		// css
	private String width;					// 整个组件的宽度
	
	// ---- 内部参数
	// 其它
	private String _refFull; 	// 完整的客户端引用id
	private String _forFull; 	// 完整的目标组件id
	private Boolean autoLoad;	// 是否自动装载数据
	private String styleOfPanel;// 组件主要区域的样式，包括content及scroller
	
	// 组件的ID参数
	private String openButtonId;			// 组件的开关图标区域的id,用于打开或关闭inner区域
	private String innerPanelId;
	private String contentPanelId;			// 数据记录区域的id
	private String scrollerPanelTopId;		// 翻页组件区域的id(top)
	private String scrollerPanelBottomId; 	// 翻页组件区域的id(bottom)
	
	// 翻页图片src
	private String img_min_firstId;
	private String img_min_lastId;
	private String img_min_nextId;
	private String img_min_previousId;
	
	private String img_min_first;
	private String img_min_last;
	private String img_min_next;
	private String img_min_previous;
	
    public void setSearch(MethodExpression search) {
        this.search = search;
    }

    @Override
    public Object saveState(FacesContext fc) {
        Object[] objs = new Object[3];
        objs[0] = super.saveState(fc);
        objs[1] = this.search;
        objs[2] = this.editListener;
        return objs;
    }

    @Override
    public void restoreState(FacesContext fc, Object state) {
        Object[] objs = (Object[]) state;
        super.restoreState(fc, objs[0]);
        this.search = (MethodExpression) objs[1];
        this.editListener = (MethodExpression) objs[2];
    }
    
	@Override
    public void encodeBegin(FacesContext fc) throws IOException {
		super.encodeBegin(fc);
		
		// 载入资源
		QFaces.getResLoader2().loadJavaScript(this, "MiniDataTable.js");
		
		// 定义ID
		String clientId = this.getClientId(fc);
		this.openButtonId = clientId + ":openButton";
		this.innerPanelId = clientId + ":innerPanel";
		this.contentPanelId = clientId + ":contentPanel";
		this.scrollerPanelTopId = clientId + ":scrollerPanelTop";
		this.scrollerPanelBottomId = clientId + ":scrollerPanelBottom";		
		
		// 处理参数
		this._ref = (String) this.getAttributes().get("ref");
		this._for = (String) this.getAttributes().get("for");
		this._refFull = this.processToFullId(fc, this._ref);
		this._forFull = this.processToFullId(fc, this._for);
		this._sortFields = (String) QFaces.getAttribute(this, "sortFields", "");
		this._pageSize = QFaces.convertToInteger(QFaces.getAttribute(this, "pageSize", 10));
		this._displayPage = QFaces.convertToInteger(QFaces.getAttribute(this, "displayPage", 5));
		this._strHeaders = (String) QFaces.getAttribute(this, "strHeaders", "");
		this._strOpenButtonTip = (String) QFaces.getAttribute(this, "strOpenButtonTip", "打开窗口");
		this._autoClose = QFaces.convertToBoolean(QFaces.getAttribute(this, "autoClose", Boolean.TRUE));
		this._searchWhenOpen = QFaces.convertToBoolean(QFaces.getAttribute(this, "searchWhenOpen", Boolean.TRUE));
		this.ascSrc = (String) this.getAttributes().get("ascSrc");
		this.descSrc = (String) this.getAttributes().get("descSrc");
		this.displayJump = QFaces.convertToBoolean(QFaces.getAttribute(this, "displayJump", Boolean.FALSE));
		this.strJump = (String) QFaces.getAttribute(this, "strJump", "Jump");
		this.displayInfo = QFaces.convertToBoolean(QFaces.getAttribute(this, "displayInfo", Boolean.FALSE));
		this.strRecordTotal = (String) QFaces.getAttribute(this, "strRecordTotal", "Record:");
		this.strPageTotal = (String) QFaces.getAttribute(this, "strPageTotal", "Page:");
		this.scrollerOn = (String) QFaces.getAttribute(this, "scrollerOn", "bottom");
		this.alwaysShowScroller = QFaces.convertToBoolean(QFaces.getAttribute(this, "alwaysShowScroller", Boolean.TRUE));
		this.scrollerClass = (String) QFaces.getAttribute(this, "scrollerClass", "");
		this.editColumns = (String) QFaces.getAttribute(this, "editColumns", "");
		this.alwaysShow = QFaces.convertToBoolean(QFaces.getAttribute(this, "alwaysShow", false));
		
        // 是否显示display按钮,在alwaysShow=true的情况下可以不显示display按钮
        this.showOpenButton = QFaces.convertToBoolean(QFaces.getAttribute(this, "showOpenButton", true));
        
        this.border = (String) QFaces.getAttribute(this, "border", "0");
        this.styleClass = (String) QFaces.getAttribute(this, "styleClass", "");
        this.headerClass = (String) QFaces.getAttribute(this, "headerClass", "");
        this.rowClasses = (String) QFaces.getAttribute(this, "rowClasses", "");
        this.columnClasses = (String) QFaces.getAttribute(this, "columnClasses", "");
        this.rowOnClass = (String) QFaces.getAttribute(this, "rowOnClass", "");
		
		// 用于指定宽度，在某些浏览器下面如：IE7，open模式下可能会出现宽度不正常的情况
		// 可通过指定width属性来解决该问题
		this.width = (String) this.getAttributes().get("width");
		
		this.scrollerInfoClass = (String) QFaces.getAttribute(this, 
				"scrollerInfoClass", "_qf_02_infoClass");
		this.scrollerPageClass = (String) QFaces.getAttribute(this, 
				"scrollerPageClass", "_qf_02_pageClass");
		this.scrollerPageCurrentClass = (String) QFaces.getAttribute(this, 
				"scrollerPageCurrentClass", "_qf_02_pageCurrentClass");
		this.scrollerPageOnClass = (String) QFaces.getAttribute(this, 
				"scrollerPageOnClass", "_qf_02_pageOnClass");

		if (_refFull == null) _refFull = "";
		if (_forFull == null) _forFull = "";
		if (!"top".equalsIgnoreCase(scrollerOn) && !"both".equalsIgnoreCase(scrollerOn)) {
			scrollerOn = "bottom";
		}
		
		this.img_min_firstId = clientId + ":img_min_first";
		this.img_min_lastId = clientId + ":img_min_last";
		this.img_min_nextId = clientId + ":img_min_next";
		this.img_min_previousId = clientId + ":img_min_previous";
        this.img_min_first = QFaces.getResLoader2().makeImageSrc("02-first.gif");
        this.img_min_last = QFaces.getResLoader2().makeImageSrc("02-last.gif");
        this.img_min_next = QFaces.getResLoader2().makeImageSrc("02-next.gif");
        this.img_min_previous = QFaces.getResLoader2().makeImageSrc("02-previous.gif");
        if (this.ascSrc == null) {
        	this.ascSrc = QFaces.getResLoader2().makeImageSrc("02-asc.png");
        }
        if (this.descSrc == null) {
        	this.descSrc = QFaces.getResLoader2().makeImageSrc("02-desc.png");
        }
        
        
        // "总是显示"表示了组件将以一般的table方式运作，所以autoLoad=true,即组件自动装载数据，不需要触发openButton
        // 是否总是显示组件,如果“总是显示”那么组件的position不应该为absolute,应该占用一定的空间,
        if (this.alwaysShow) {
        	this.styleOfPanel = "display:block;";
        	this.autoLoad = true;
        } else {
        	this.styleOfPanel = "border:0;position:absolute;display:none;background:white;";
        	this.autoLoad = false;
        }
        // 处理宽度属性可能存在的bug
        if (this.width != null) {
        	this.styleOfPanel += "width:" + this.width;
        	if (this.width.indexOf("%") <= 0 && this.width.indexOf("px") <= 0) {
        		this.styleOfPanel += "px";
        	}
        	this.styleOfPanel += ";";
        }
       
		// 渲染输出
		encodeInputHidden(fc, this.img_min_firstId, this.img_min_first);
		encodeInputHidden(fc, this.img_min_lastId, this.img_min_last);
		encodeInputHidden(fc, this.img_min_nextId, this.img_min_next);
		encodeInputHidden(fc, this.img_min_previousId, this.img_min_previous);
		
		ResponseWriter rw = fc.getResponseWriter();
		rw.startElement("div", this);
		rw.writeAttribute("id", clientId, null);
		rw.writeAttribute("name", clientId, null);
		rw.writeAttribute("style", "border:0px solid red;margin:0px;padding:0px;", null);
		if (this.showOpenButton) {
			encodeOpenButton(fc);
		}
			rw.startElement("div", this);
			rw.writeAttribute("id", this.innerPanelId, null);
			rw.writeAttribute("name", this.innerPanelId, null);
			rw.writeAttribute("style", this.styleOfPanel, null);
	}
	
	@Override
    public void encodeChildren(FacesContext fc) throws IOException {
		super.encodeChildren(fc);
	}
	
	@Override
	public void encodeEnd(FacesContext fc) throws IOException {
		super.encodeEnd(fc);
		ResponseWriter rw = fc.getResponseWriter();
		
			encodeScrollerPanel(fc, this.scrollerPanelTopId);
			encodeContentPanel(fc);
			encodeScrollerPanel(fc, this.scrollerPanelBottomId);
			rw.endElement("div"); // end "clientId:inner"
		rw.endElement("div"); // end "clientId"
		
		encodeInit(fc);
	}
	
	// ---- Private encodes
	
	private void encodeOpenButton(FacesContext fc) throws IOException {
		ResponseWriter rw = fc.getResponseWriter();
		rw.startElement("div", this);
		rw.writeAttribute("id", this.openButtonId, null);
		rw.writeAttribute("name", this.openButtonId, null);
		rw.writeAttribute("title", this._strOpenButtonTip, null);
		rw.writeAttribute("style", "cursor:pointer;", null);
		QFaces.getResLoader2().loadImage("02-search.gif");
		rw.endElement("div");
	}
	
	private void encodeContentPanel(FacesContext fc) throws IOException {
		ResponseWriter rw = fc.getResponseWriter();
		rw.startElement("div", this);
		rw.writeAttribute("id", this.contentPanelId, null);
		rw.writeAttribute("name", this.contentPanelId, null);
		rw.writeAttribute("style", "border:0px solid green;", null);
		rw.endElement("div");
	}
	
	private void encodeScrollerPanel(FacesContext fc, String id) throws IOException {
		ResponseWriter rw = fc.getResponseWriter();
		rw.startElement("div", this);
		rw.writeAttribute("id", id, null);
		rw.writeAttribute("name", id, null);
		rw.writeAttribute("style", "border:0px solid blue;", null);
		rw.endElement("div");
	}
	
	private void encodeInputHidden(FacesContext fc, String id, String value) throws IOException {
		ResponseWriter rw = fc.getResponseWriter();
		rw.startElement("input", this);
		rw.writeAttribute("id", id, null);
		rw.writeAttribute("name", id, null);
		rw.writeAttribute("type", "hidden", null);
		rw.writeAttribute("value", value, null);
		rw.endElement("input");
	}
	
	private void encodeInit(FacesContext fc) throws IOException {
		if (this.search == null)
			throw new NullPointerException("Attribute \"search\" not found! " +
					"The attribute named \"search\" was required(MiniDataTable)");
		StringBuilder sb = new StringBuilder("Q.F.UIMiniDataTable.init(")
		.append(" \"" + this.getClientId(fc) + "\"")
		.append(",\"" + this._refFull + "\"")
		.append(",\"" + this._forFull + "\"")
		.append(",\"" + this.search.getExpressionString() + "\"")
		.append(",\"" + this.getClass().getName() + "\"")
		.append(",\"" + this._pageSize + "\"")
		.append(",\"" + this._displayPage + "\"")
		.append(",\"" + this._strHeaders + "\"") 
		.append(",\"" + this._autoClose.toString() + "\"")
		.append(",\"" + this._searchWhenOpen.toString() + "\"")
		.append(",\"" + this.autoLoad.toString() + "\"")
		.append(",\"" + this.alwaysShow.toString() + "\"")
		.append(",\"" + this.border + "\"")
		.append(",\"" + this.styleClass + "\"")
		.append(",\"" + this.headerClass + "\"")
		.append(",\"" + this.rowClasses + "\"")
		.append(",\"" + this.columnClasses + "\"")
		.append(",\"" + this.rowOnClass + "\"")
		.append(",\"" + this._sortFields + "\"")
		.append(",\"" + this.ascSrc + "\"")
		.append(",\"" + this.descSrc + "\"")
		.append(",\"" + this.displayJump.toString() + "\"")
		.append(",\"" + this.strJump + "\"")
		.append(",\"" + this.strRecordTotal + "\"")
		.append(",\"" + this.strPageTotal + "\"")
		.append(",\"" + this.displayInfo.toString() + "\"")
		.append(",\"" + this.openButtonId + "\"")
		.append(",\"" + this.innerPanelId + "\"")
		.append(",\"" + this.contentPanelId + "\"")
		.append(",\"" + this.scrollerPanelTopId + "\"")
		.append(",\"" + this.scrollerPanelBottomId + "\"")
		.append(",\"" + this.scrollerOn + "\"")
		.append(",\"" + this.alwaysShowScroller.toString() + "\"")
		.append(",\"" + this.scrollerClass + "\"")
		.append(",\"" + this.scrollerInfoClass + "\"")
		.append(",\"" + this.scrollerPageClass + "\"")
		.append(",\"" + this.scrollerPageCurrentClass + "\"")
		.append(",\"" + this.scrollerPageOnClass + "\"")
		.append(",\"" + this.editColumns + "\"")
		.append(",\"" + (this.editListener != null ? this.editListener.getExpressionString() : "") + "\"")
		.append(");");
		QFaces.getResLoader2().loadJavaScriptInit(this, sb.toString());
	}
	
	// ---- Ajax process
	
	public void ajax(Map<String, String> params, FacesContext fc) {
        logger.fine("Ajax invoke on get start...");
        String ajaxMode = params.get("ajaxMode");
        if ("editRow".equalsIgnoreCase(ajaxMode)) {
        	logger.fine("\"editRow\" mode start");
        	String editListenerExp = params.get("editListenerExp");
        	Integer rowIndex = Integer.parseInt(params.get("rowIndex"));
        	Integer columnCount = Integer.parseInt(params.get("columnCount"));
        	String[] fields = new String[columnCount];
        	for (int i = 0; i < columnCount; i++) {
        		fields[i] = params.get("value_" + rowIndex + "_" + i);
        	}
        	MethodExpression _editListener = QFaces.createMethodExpression(
        			editListenerExp, String.class, new Class[]{String[].class});
        	String result = (String) _editListener.invoke(fc.getELContext(), new Object[]{fields});
        	if (result == null)
        		result = "";
        	QFaces.getResponse().responseHTML(fc, result);
        } else { // "search" mode
        	logger.fine("\"search\" mode start");
            String searchExp = params.get("searchExp");
            Integer start = QFaces.convertToInteger(params.get("start"));
            Integer pageSize = QFaces.convertToInteger(params.get("pageSize"));
            String sortField = params.get("sortField");
            Boolean asc = QFaces.convertToBoolean(params.get("asc"));
            if ("".equals(sortField)) {
            	sortField = null;
            }
            MethodExpression _search = QFaces.createMethodExpression(searchExp, MiniPageModel.class, new Class[]{MiniPageParam.class});
            Integer refCount = Integer.valueOf(params.get("refCount"));
            if (refCount == null) refCount = 0;
            String[] args = new String[refCount];
            for (int i = 0; i < refCount; i++) {
                String refValue = params.get("refValue" + i);
                args[i] = refValue;
            }
            MiniPageParam mpp = new MiniPageParam(start, pageSize, sortField, asc, args);
            MiniPageModel mpm = (MiniPageModel) _search.invoke(fc.getELContext(), new Object[]{mpp});
            if (mpm == null) {
            	mpm = new MiniPageModel();
            }
            response(fc, mpm);
        }
        logger.fine("End of ajax invoke on get.");
	}
	
	/**
	 * <global total="" pageSize="" />
	 * <values>
	 * 		<value>
	 * 			<val value="" />
	 * 			<val value="" />
	 * 		</value>
	 * 		<value>
	 * 			<val value="" />
	 * 			<val value="" />
	 * 		</value>
	 * </values>
	 * @param fc
	 * @param mpm
	 */
	private void response(FacesContext fc, MiniPageModel mpm) {
        try {
            Document doc = XmlOper.newDocument();
            Element nodeRoot = doc.createElement("root");
            doc.appendChild(nodeRoot);
            // Scroller info
            Element global = doc.createElement("global");
            nodeRoot.appendChild(global);
            global.setAttribute("total", String.valueOf(mpm.getTotal()));
            Element values = doc.createElement("values");
            nodeRoot.appendChild(values);
            List<List<String>> records = mpm.getRecords();
            for (List<String> record : records) {
            	Element value = doc.createElement("value");
            	values.appendChild(value);
            	for (String field : record) {
            		Element val = doc.createElement("val");
            		value.appendChild(val);
            		val.setAttribute("value", field);
            	}
            }
            QFaces.getResponse().responseXML(fc, doc);
        } catch (ParserConfigurationException e) {
            logger.log(Level.SEVERE, null, e);
        }
    }

	/**
	 * sortClientIds 由客户端短id组成(由","号分隔)
	 */
	private String processToFullId(FacesContext fc, String sortClientIds) {
		if (sortClientIds == null) return null;
		String result = ""; // result格式"clientId1,clientId2,clientId3"
		String[] sortIds = sortClientIds.split(",");
		for (String sortId : sortIds) {
			if (sortId == null || "".equals(sortId.trim())) {
				result += ",";
			} else {
				UIComponent ui = this.findComponent(sortId.trim());
				// 这里ui可能为null,出现这样的情况主要是 sortClientIds的格式原因,如： id1,,,id2  即中间可能有空id
				if (ui != null) {
					result += ui.getClientId(fc) + ",";
				} else {
					result += ",";
				}
			}
		}
		if (result.endsWith(",")) {
			result = result.substring(0, result.length() - 1);
		}
		return result;
	}
	
	// -----------------------------------------------------
	private MethodExpression editListener;
	private String editColumns;	// Format: 0,1,1,...  0 表示该列不能被编辑，1表示该列可编辑
	
    public void setEditListener(MethodExpression editListener) {
        this.editListener = editListener;
    }

	@Override
	public String getFamily() {
		return getClass().getName();
	}
	
}
